#include "lightning.h"
#include "graphics/graphics.h"
#include "graphics/graphics_util.h"
#include "num/rand.h"

#include "dd/dd_man.h"

#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		    

#include <cassert>
#include <stdlib.h> 

using namespace vfx;



Lightning::Lightning(EffectMan*		pParent) :
		SFXSet(pParent),
	m_Jitter(0.14f),
	m_JitterReduction(0.3f),
	m_Recursions(5),
	m_BranchProb(30),
	m_BranchAlpha(0.3f),
	m_BranchLength(0.55f),
	m_BranchDeviation(0.2f),
	m_Duration(1.0f/10.0f),
	m_RepeatC(3),
	m_RayWidth(1)
{
	m_Sprite.Create("effects/lightning.png");
}

Lightning::~Lightning()
{

}


int		Lightning::GetIterations(float Dist)
{
	if (Dist < 0.1f) return 3;
	if (Dist < 0.2f) return 4;
	if (Dist < 0.4f) return 5;
	return 6;
}

Lightning::Bolt*	Lightning::StartBolt(const math::Vec2& From, const math::Vec2& To)
{
	Bolt*	pBolt = new Bolt;	// store and recycle instead?
	pBolt->Delay.SetLimit(m_Duration*0.5f);	// first bolt has no delay, so remember to force it
	pBolt->Duration.SetLimit(m_Duration);
	pBolt->From = From;
	pBolt->To = To;
	pBolt->RegenC = m_RepeatC;
	pBolt->RegenI = 0;
	pBolt->IsMain = true;
	Generate(pBolt, From, To, m_Jitter, GetIterations((To-From).Magnitude()), 1);
	while (!pBolt->Chunks.empty())
	{
		ChunkData&	ChunkInfo = pBolt->Chunks[0];
		Generate(pBolt, ChunkInfo.From, ChunkInfo.To, ChunkInfo.Jitter, ChunkInfo.Iterations, ChunkInfo.Alpha);
		pBolt->Chunks.erase(pBolt->Chunks.begin());
	}
	m_Bolts.push_back(pBolt);
	return pBolt;
}
void	Lightning::TriggerBolt(const math::Vec2& From, const math::Vec2& To, RayCB pCB, void* pData)
{
	Bolt*	pB1 = StartBolt(From, To);
	Bolt*	pB2 = StartBolt(From, To);
	
	pB1->pCB = pCB;
	pB1->pData = pData;
	pB1->IsMain = true;	// the other is a shadow

	pB2->pCB = pCB;
	pB2->pData = pData;
	pB2->IsMain = false;
	// first bolt has no delay
	pB1->Delay.ForceReady();
}

void	Lightning::Tick(float dt)
{		
	for (int i = (int)(m_Bolts.size())-1; i>=0; i--)
	{
		Bolt*	pBolt = m_Bolts[i];
		if (pBolt->Delay.IsReady() && pBolt->Duration.IsReady())
		{
			pBolt->RegenI++;
			if (pBolt->IsMain && pBolt->pCB)
			{
				pBolt->pCB(pBolt->From, pBolt->To, pBolt->RegenI, pBolt->pData);
			}
		}
	}

	for (int i = (int)(m_Bolts.size())-1; i>=0; i--)
	{
		Bolt*	pBolt = m_Bolts[i];
		if (pBolt->Delay.IsReady())
		{
			if (pBolt->Duration.IsReady())	// done?
			{
				if (pBolt->RegenC > pBolt->RegenI)
				{
					
					// reset
					pBolt->Segments.resize(0);
			//		pBolt->Delay.Reset();
					pBolt->Duration.Reset();
			
					Generate(pBolt, pBolt->From, pBolt->To, m_Jitter, GetIterations((pBolt->To-pBolt->From).Magnitude()), 1);
					while (!pBolt->Chunks.empty())
					{
						ChunkData&	ChunkInfo = pBolt->Chunks[0];
						Generate(pBolt, ChunkInfo.From, ChunkInfo.To, ChunkInfo.Jitter, ChunkInfo.Iterations, ChunkInfo.Alpha);
						pBolt->Chunks.erase(pBolt->Chunks.begin());
					}
				} else
				{
					// okay, it's done. Fire and forget
					delete pBolt;
					m_Bolts.erase(m_Bolts.begin()+i);
				}
			} else
			{
				pBolt->Duration.Tick(dt);
			}
		} else
		{
			pBolt->Delay.Tick(dt);
		}
	}
}

void	FlushCache(Graphics::Vertex2D* VertA, int& VertC)
{
	if (VertC <= 0) return;
	GetGraphics()->DrawVertex2D(VertA, VertC);
	VertC = 0;
}	
void	Lightning::Render(const math::Vec2&	Offset, float Scale)
{
	if (m_Bolts.empty()) return;
	Graphics::Vertex2D*	VertA = Graphics::GetTempVert();
	int		VertC = 0;
	math::Vec2			Jitter = num::RAND32::Instance().RandVec2( 2*GraphicsUtil::InvW*Scale, 2*GraphicsUtil::InvH*Scale);
//	GetGraphics()->SetupAdditiveAlpha(true);
	m_Sprite.SetTexture();
	static const int				VERTC = 4;
	math::Vec2						PosA[VERTC];
	math::Vec2						UVA[VERTC];
	COLOR							ColorA[VERTC];
	UVA[0] = math::Vec2(m_Sprite.GetUV1().x, m_Sprite.GetUV1().y);
	UVA[1] = math::Vec2(m_Sprite.GetUV2().x, m_Sprite.GetUV1().y);
	UVA[2] = math::Vec2(m_Sprite.GetUV1().x, m_Sprite.GetUV2().y);
	UVA[3] = math::Vec2(m_Sprite.GetUV2().x, m_Sprite.GetUV2().y);

	for (unsigned int i=0; i<m_Bolts.size(); i++)
	{
		Bolt*	pBolt = m_Bolts[i];
		if (!pBolt->Delay.IsReady()) continue;
		float	Prog = pBolt->Duration.GetProgress();
		// render bolt as sequences here
		for (unsigned int j=0; j<pBolt->Segments.size(); j++)
		{
			RaySegment& Segment = pBolt->Segments[j];
			math::Vec2	From = (Segment.From+Jitter - Offset)*Scale;
			math::Vec2	To = (Segment.To+Jitter - Offset)*Scale;
			//Segment.Width = 1.0f - (j / (float)pBolt->Segments.size());
			//Segment.Width = 1.0f;
			float	Width = GraphicsUtil::InvW * m_RayWidth;
/*			if (Prog > 0.4f)
			{
				Width = (Prog * 3.0f) * GraphicsUtil::InvW * Scale;
			}*/	

			COLOR		c = ColorfRGBA(1, 1, 1, (1-Prog) * Segment.Alpha);
			for (int k=0; k<VERTC; k++)
				ColorA[k]=c;
			if (VertC + 12 >= Graphics::MAX_TEMP_VERT)	FlushCache(VertA, VertC);		// stop overruns

			// normal to parent?
			math::Vec2	N1 = Segment.Normal* Width;
			math::Vec2	N2 = Segment.Normal* Width;
			if (j > 0)
			{
				N1 = (Segment.Normal + pBolt->Segments[j-1].Normal) * 0.5f * Width;
				//dd::Manager::Instance().AddLine2D(From, From+Normal, ColorfRGBA(1, 1, 0, (1-Prog) * Segment.Alpha));
			}
			if (j < pBolt->Segments.size()-1)
			{
				N2 = (Segment.Normal + pBolt->Segments[j+1].Normal) * 0.5f * Width;
			}

			// setup posa
			PosA[0]= From - N1;
			PosA[1]= From + N1;
			PosA[2]= To -  N2;
			PosA[3]= To + N2;

			VertC += GraphicsUtil::FillQuad(PosA, UVA, ColorA, &VertA[VertC]);
			//VertC += GraphicsUtil::FillLine(From, To, Width, c, c, &CacheA[VertC]);
			/*dd::Manager::Instance().AddLine2D(PosA[0], PosA[1], c);
			dd::Manager::Instance().AddLine2D(PosA[1], PosA[2], c);
			dd::Manager::Instance().AddLine2D(PosA[2], PosA[3], c);
			dd::Manager::Instance().AddLine2D(PosA[3], PosA[0], c);*/
		}
	}
	FlushCache(VertA, VertC);
//	GetGraphics()->SetupAdditiveAlpha(false);
}

void	Lightning::RenderDebug(const math::Vec2&	Offset, float Scale)
{
	num::RAND32& Rnd = num::RAND32::Instance();
	for (unsigned int i=0; i<m_Bolts.size(); i++)
	{
		Bolt*	pBolt = m_Bolts[i];
		if (!pBolt->Delay.IsReady()) continue;
		float	Prog = pBolt->Duration.GetProgress();
		// render bolt as sequences here
		for (unsigned int j=0; j<pBolt->Segments.size(); j++)
		{
			RaySegment& Segment = pBolt->Segments[j];
		//	math::Vec2	From = (Segment.From + Rnd.RandVec2(-Segment.Jitter, Segment.Jitter)- Offset)*Scale;
		//	math::Vec2	To = (Segment.To + Rnd.RandVec2(-Segment.Jitter, Segment.Jitter)- Offset)*Scale;

			math::Vec2	From = (Segment.From - Offset)*Scale;
			math::Vec2	To = (Segment.To - Offset)*Scale;
			dd::Manager::Instance().AddLine2D(From, To, ColorfRGBA(1, 1, 0, (1-Prog) * Segment.Alpha));
		}
	}
}

void	Lightning::Generate(Bolt* pBolt, const math::Vec2& From, const math::Vec2& To, float Jitter, int	Recursions, float Alpha)
{
	// if Recursions > 0 generate two new recursions
	// if Prob == branch, generate another branch from the center of the two
	// otherwise, add to set

	if (Recursions == 0)
	{
		RaySegment	Seg;
		Seg.From  = From;
		Seg.To	  = To;
		Seg.Normal = math::Vec2(-(To.y-From.y), To.x-From.x);
		Seg.Normal.Normalize();
		Seg.Alpha = Alpha;
		Seg.Jitter = Jitter;
		//Seg.Width = (To - From).Magnitude();
		// shrink the width by how close to the destination it is
		pBolt->Segments.push_back(Seg);	// use arrays here instead?
		// terminus
		return;	
	}
	
	// generate midpoint		
	math::Vec2	Delta = To-From;
	math::Vec2	Right(-Delta.y, Delta.x);
	float		NextJitter = Jitter * m_JitterReduction;
	float		ApplyJitter = num::RAND32::Instance().RandFloat(-Jitter, Jitter);	
	math::Vec2	P = (From + (Delta * 0.5f)) + (Right * ApplyJitter);

	// generate the segments
	Generate(pBolt, From, P, NextJitter, Recursions-1, Alpha);
	Generate(pBolt, P, To, NextJitter, Recursions-1, Alpha);


	// generate branch?
	if ((rand() % 100)  < m_BranchProb)
	{
		float		BranchOffset = m_BranchDeviation; //num::RAND32::Instance().RandFloat(-m_BranchDeviation, m_BranchDeviation);
		if (rand() %2)
		{
			BranchOffset = -BranchOffset;
		}
		math::Vec2	BranchDir = P-From;
		math::Vec2	BranchTo = P + (BranchDir * m_BranchLength) + (Right * BranchOffset);
		
		ChunkData	ChunkInfo;
		ChunkInfo.From = P;
		ChunkInfo.To = BranchTo;
		ChunkInfo.Jitter = NextJitter;
		ChunkInfo.Iterations = Recursions-1;
		ChunkInfo.Alpha  = Alpha * m_BranchAlpha;

		pBolt->Chunks.push_back(ChunkInfo);
		//Generate(pBolt, P, BranchTo, NextJitter, Recursions-1, Alpha * m_BranchAlpha);
	}
}
